1 module hip.api.renderer.shadervar; 2 import hip.api.renderer.core; 3 import hip.api.renderer.shader; 4 import hip.api.graphics.color; 5 public import hip.api.renderer.shadervar; 6 7 /** 8 * Changes how the Shader behaves based on the backend 9 */ 10 enum ShaderHint : uint 11 { 12 NONE = 0, 13 GL_USE_BLOCK = 1<<0, 14 GL_USE_STD_140 = 1<<1, 15 D3D_USE_HLSL_4 = 1<<2, 16 /** 17 * Meant for usage in uniform variables. 18 * That means one Shader Variable may not be sent to the backend depending on its requirements. 19 * An example for that is Array of Textures. In D3D11, it depends only on the resource being bound, 20 * while on Metal and GL3, they are required to be inside a MTLBuffer or being sent as an Uniform. 21 */ 22 Blackbox = 1 << 3, 23 MaxTextures = 1 << 4 24 } 25 26 /** 27 * Should not be used directly. The D type inference can already set that for you. 28 * This is stored by the variable to know how to access itself and comunicate the shader. 29 */ 30 enum UniformType : ubyte 31 { 32 boolean, 33 integer, 34 integer_array, 35 uinteger, 36 uinteger_array, 37 floating, 38 floating2, 39 floating3, 40 floating4, 41 floating2x2, 42 floating3x3, 43 floating4x4, 44 floating_array, 45 ///Special type that is implemented by renderers backend 46 texture_array, 47 none 48 } 49 50 UniformType uniformTypeFrom(T)() 51 { 52 import hip.util.reflection; 53 54 with(UniformType) 55 { 56 static if(is(T == bool)) return boolean; 57 else static if(is(T == int)) return integer; 58 else static if(is(T == int[])) return integer_array; 59 else static if(is(T == uint) || is(T == HipColor)) return uinteger; 60 else static if(is(T == uint[])) return uinteger_array; 61 else static if(is(T == float)) return floating; 62 else static if(is(T == float[2])) return floating2; 63 else static if(is(T == float[3])) return floating3; 64 else static if(is(T == float[4]) || is(T == HipColorf)) return floating4; 65 else static if(isTypeArrayOf!(float, T, 9)) return floating3x3; 66 else static if(isTypeArrayOf!(float, T, 16)) return floating4x4; 67 else static if(is(T == float[])) return floating_array; 68 else static if(is(T == IHipTexture[])) return texture_array; 69 else return none; 70 } 71 } 72 73 /** 74 * Struct that holds uniform/cbuffer information for Direct3D and OpenGL shaders. It can be any type. 75 * Its data is accessed by the ShaderVariableLayout when sendVars is called. Thus, depending on its 76 * corrensponding type, its data is uploaded to the GPU. 77 */ 78 struct ShaderVar 79 { 80 import hip.util.data_structures:Array; 81 import std.traits; 82 void[] data; 83 string name; 84 ShaderTypes shaderType; 85 UniformType type; 86 size_t singleSize; 87 bool isDynamicArrayReference; 88 bool isDirty = true; 89 90 ShaderHint flags; 91 ShaderVariablesLayout layout; 92 public bool isBlackboxed() const { return (flags & ShaderHint.Blackbox) != 0;} 93 public bool usesMaxTextures() const { return (flags & ShaderHint.MaxTextures) != 0;} 94 95 96 size_t varSize() const{return data.length;} 97 size_t length() const {return varSize / singleSize;} 98 99 const T get(T)() 100 { 101 static if(isDynamicArray!T) 102 return cast(T)data; 103 else 104 return *(cast(T*)this.data.ptr); 105 } 106 107 108 private void setDirty() 109 { 110 this.isDirty = true; 111 this.layout.isDirty = true; 112 } 113 114 bool setBlackboxed(T)(T value) 115 { 116 import core.stdc.string; 117 if(value.sizeof != varSize || !isBlackboxed) return false; 118 setDirty(); 119 memcpy(data.ptr, &value, varSize); 120 return true; 121 } 122 bool set(T)(T value, bool validateData) 123 { 124 import core.stdc.string; 125 static assert(uniformTypeFrom!T != UniformType.none, "Invalid type "~T.stringof); 126 static if(isDynamicArray!T) 127 { 128 memcpy(data.ptr, value.ptr, value.length * T.init[0].sizeof); 129 } 130 else 131 { 132 if(value.sizeof != varSize) 133 return false; 134 if(!isBlackboxed && validateData) 135 { 136 import hip.math.matrix; 137 auto current = get!T; 138 static if(is(T == Matrix3) || is(T == Matrix4)) 139 { 140 if(value == current || value == current.transpose) 141 return true; 142 } 143 else 144 { 145 if(value == current) 146 return true; 147 } 148 } 149 memcpy(data.ptr, &value, varSize); 150 } 151 setDirty(); 152 return true; 153 } 154 155 private void throwOnOutOfBounds(size_t index) 156 { 157 import hip.util.conv:to; 158 switch(type) with(UniformType) 159 { 160 case boolean, integer, uinteger, floating: 161 throw new Exception("Unsupported type '"~type.to!string~"' for indexing on shader vairable "~name); 162 default: 163 if(index >= this.length) 164 throw new Exception("Index "~index.to!string~" ot of range [0.."~length.to!string~"] on shader variable "~name); 165 } 166 } 167 168 auto opIndexAssign(T)(T value, size_t index) 169 { 170 import hip.util.conv:to; 171 import core.stdc.string; 172 throwOnOutOfBounds(index); 173 if(index * singleSize + T.sizeof > varSize) 174 { 175 throw new Exception("Value assign of type "~T.stringof~" at index "~to!string(index)~ 176 " is invalid for shader variable "~name~" of type "~to!string(type)); 177 } 178 179 memcpy(cast(ubyte*)data + singleSize*index, &value, T.sizeof); 180 return value; 181 } 182 183 ref auto opIndex(size_t index) 184 { 185 throwOnOutOfBounds(index); 186 switch(type) with(UniformType) 187 { 188 case integer_array: return get!(int[])[index]; 189 case uinteger_array: return get!(uint[])[index]; 190 case floating_array: return get!(float[])[index]; 191 case floating2: return get!(float[2])[index]; 192 case floating3: return get!(float[3])[index]; 193 case floating4: return get!(float[4])[index]; 194 case floating2x2: return get!(float[4])[index]; 195 case floating3x3: return get!(float[9])[index]; 196 case floating4x4: return get!(float[16])[index]; 197 default: return 0; 198 } 199 } 200 201 static ShaderVar* create(ShaderTypes t, string varName, bool data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.boolean, data.sizeof, data.sizeof, layout);} 202 static ShaderVar* create(ShaderTypes t, string varName, int data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.integer, data.sizeof, data.sizeof, layout);} 203 static ShaderVar* create(ShaderTypes t, string varName, uint data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.uinteger, data.sizeof, data.sizeof, layout);} 204 static ShaderVar* create(ShaderTypes t, string varName, float data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.floating, data.sizeof, data.sizeof, layout);} 205 static ShaderVar* create(ShaderTypes t, string varName, float[2] data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.floating2, data.sizeof, data[0].sizeof, layout);} 206 static ShaderVar* create(ShaderTypes t, string varName, float[3] data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.floating3, data.sizeof, data[0].sizeof, layout);} 207 static ShaderVar* create(ShaderTypes t, string varName, float[4] data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.floating4, data.sizeof, data[0].sizeof, layout);} 208 static ShaderVar* create(ShaderTypes t, string varName, float[9] data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.floating3x3, data.sizeof, data[0].sizeof, layout);} 209 static ShaderVar* create(ShaderTypes t, string varName, float[16] data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.floating4x4, data.sizeof, data[0].sizeof, layout);} 210 static ShaderVar* create(ShaderTypes t, string varName, int[] data, ShaderVariablesLayout layout) 211 { 212 return ShaderVar.create(t, varName, data.ptr, UniformType.floating_array, int.sizeof*data.length, int.sizeof, layout, true); 213 } 214 static ShaderVar* create(ShaderTypes t, string varName, uint[] data, ShaderVariablesLayout layout) 215 { 216 return ShaderVar.create(t, varName, data.ptr, UniformType.floating_array, uint.sizeof*data.length, uint.sizeof, layout, true); 217 } 218 static ShaderVar* create(ShaderTypes t, string varName, float[] data, ShaderVariablesLayout layout) 219 { 220 return ShaderVar.create(t, varName, data.ptr, UniformType.floating_array, float.sizeof*data.length, float.sizeof, layout, true); 221 } 222 223 protected static ShaderVar* create( 224 ShaderTypes t, 225 string varName, 226 void* varData, 227 UniformType type, 228 size_t varSize, 229 size_t singleSize, 230 ShaderVariablesLayout layout, 231 bool isDynamicArrayReference=false 232 ) 233 { 234 ShaderVar* s = createEmpty(t, varName, type, varSize, singleSize, layout); 235 s.data[0..varSize] = varData[0..varSize]; 236 s.isDynamicArrayReference = isDynamicArrayReference; 237 return s; 238 } 239 public static ShaderVar* createEmpty( 240 ShaderTypes t, 241 string varName, 242 UniformType type, 243 size_t varSize, 244 size_t singleSize, 245 ShaderVariablesLayout layout 246 ) 247 { 248 if(!isShaderVarNameValid(varName)) 249 throw new Exception("Variable '"~varName~"' is invalid."); 250 ShaderVar* s = new ShaderVar(); 251 if(varSize != 0) 252 s.data = new void[varSize]; 253 s.name = varName; 254 s.singleSize = singleSize; 255 s.shaderType = t; 256 s.type = type; 257 s.layout = layout; 258 return s; 259 } 260 261 void dispose() 262 { 263 type = UniformType.none; 264 shaderType = ShaderTypes.none; 265 singleSize = 0; 266 if(isDynamicArrayReference) 267 { 268 (cast(Array!(int)*)data).dispose(); 269 } 270 else if(data != null) 271 { 272 import core.memory; 273 GC.free(data.ptr); 274 data = null; 275 } 276 } 277 } 278 279 struct ShaderVarLayout 280 { 281 ShaderVar* sVar; 282 size_t alignment; 283 size_t size; 284 } 285 286 /** 287 * This class is meant to be created together with the Shaders. 288 * 289 * Those are meant to wrap the cbuffer from Direct3D and Uniform Block from OpenGL. 290 * 291 * By wrapping the uniforms/cbuffers layouts, it is much easier to send those variables from any API. 292 */ 293 class ShaderVariablesLayout 294 { 295 import hip.api.renderer.var_packing; 296 297 ShaderVarLayout[string] variables; 298 private string[] namesOrder; 299 private string[] unusedBlackboxed; 300 string name; 301 ///char* representation of name 302 const(char)* nameZeroEnded; 303 protected IShader owner; 304 305 //Single block representation of variables content 306 protected void* data; 307 protected void* additionalData; 308 309 ///The hint are used for the Shader backend as a notifier 310 public immutable int hint; 311 protected size_t lastPosition; 312 313 ///A function that must return a variable size when position = 0 314 private VarPosition function( 315 ref ShaderVar* v, 316 size_t lastAlignment, 317 bool isLast 318 ) packFunc; 319 320 ShaderTypes shaderType; 321 bool isDirty = true; 322 protected bool isAdditionalAllocated; 323 ///Can't unlock Layout 324 private bool isLocked; 325 326 /** 327 * Use the layout name for mentioning the uniform/cbuffer block name. 328 * 329 * Its members are the ShaderVar* passed 330 * 331 * Params: 332 * layoutName = From which block it will be accessed on the shader 333 * t = What is the shader type that holds those variables 334 * hint = Use ShaderHint for additional information, multiple hints may be passed 335 * variables = Usually you won't pass any and use .append for writing less 336 */ 337 this(HipRendererType type, string layoutName, ShaderTypes t, uint hint, ShaderVar*[] variables ...) 338 { 339 import core.stdc.stdlib:malloc; 340 this.name = layoutName; 341 this.nameZeroEnded = (layoutName~"\0").ptr; 342 this.shaderType = t; 343 this.hint = hint; 344 345 switch(type) 346 { 347 case HipRendererType.GL3: 348 // if(hint & ShaderHint.GL_USE_STD_140) 349 packFunc = &glSTD140; 350 break; 351 case HipRendererType.D3D11: 352 // if(hint & ShaderHint.D3D_USE_HLSL_4) 353 packFunc = &dxHLSL4; 354 break; 355 case HipRendererType.Metal: 356 packFunc = &glSTD140; 357 break; 358 case HipRendererType.None: 359 default:break; 360 } 361 if(packFunc is null) packFunc = &nonePack; 362 363 foreach(ShaderVar* v; variables) 364 { 365 if(v.shaderType != t) 366 throw new Exception("ShaderVariableLayout must contain only one shader type"); 367 if(v.name in this.variables) 368 throw new Exception("Variable named "~v.name~" is already in the layout "~name); 369 this.variables[v.name] = ShaderVarLayout(v, 0, 0); 370 namesOrder~= v.name; 371 } 372 if(variables.length > 0) 373 { 374 calcAlignment(); 375 data = malloc(getLayoutSize()); 376 if(data == null) 377 throw new Exception("Out of memory"); 378 } 379 } 380 381 const(char)* nameStringz() const 382 { 383 return this.nameZeroEnded; 384 } 385 386 static ShaderVariablesLayout from(T)(HipRendererInfo info) 387 { 388 enum attr = __traits(getAttributes, T); 389 static if(is(typeof(attr[0]) == HipShaderVertexUniform)) 390 enum shaderType = ShaderTypes.vertex; 391 else static if(is(typeof(attr[0]) == HipShaderFragmentUniform)) 392 enum shaderType = ShaderTypes.fragment; 393 else static assert(false, 394 "Type "~T.stringof~" doesn't have a HipShaderVertexUniform nor " ~ 395 "HipShaderFragmentUniform attached to it." 396 ); 397 static assert( 398 attr[0].name !is null, 399 "HipShaderUniform "~T.stringof~" must contain a name as it is required to work in Direct3D 11" 400 ); 401 ShaderVariablesLayout ret = new ShaderVariablesLayout(info.type, attr[0].name, shaderType, 0); 402 static foreach(mem; __traits(allMembers, T)) 403 {{ 404 alias member = __traits(getMember, T.init, mem); 405 alias a = __traits(getAttributes, member); 406 static if(is(typeof(a[0]) == ShaderHint) && a[0] & ShaderHint.Blackbox) 407 { 408 size_t length = 1; 409 ret.appendBlackboxed(mem, uniformTypeFrom!(typeof(member)), info, length, a[0]); 410 } 411 else 412 { 413 ret.append(mem, __traits(getMember, T.init, mem)); 414 } 415 416 }} 417 418 return ret; 419 } 420 421 IShader getShader(){return owner;} 422 void lock(IShader owner) 423 { 424 calcAlignment(); 425 this.owner = owner; 426 this.isLocked = true; 427 } 428 429 /** 430 * Calculates the shader variables alignment based on the packFunc passed at startup. 431 * Those functions are based on the shader vendor and version. Align should be called 432 * always when there is a change on the layout. 433 */ 434 final void calcAlignment() 435 { 436 size_t lastAlign = 0; 437 for(int i = 0; i < namesOrder.length; i++) 438 { 439 ShaderVarLayout* l = &variables[namesOrder[i]]; 440 VarPosition pos = packFunc(l.sVar, lastAlign, i == cast(int)namesOrder.length-1); 441 l.size = pos.size; 442 l.alignment = pos.startPos; 443 lastAlign = pos.endPos; 444 } 445 lastPosition = lastAlign; 446 } 447 448 449 void* getBlockData() 450 { 451 import core.stdc.string:memcpy; 452 foreach(v; variables) 453 memcpy(data+v.alignment, v.sVar.data.ptr, v.size); 454 return data; 455 } 456 457 protected ShaderVariablesLayout append(string varName, ShaderVar* v) 458 { 459 import core.stdc.stdlib:realloc; 460 if(varName in variables) 461 throw new Exception("Variable named "~varName~" is already in the layout "~name); 462 if(isLocked) 463 throw new Exception("Can't append ShaderVariable after it has been locked"); 464 variables[varName] = ShaderVarLayout(v, 0, 0); 465 assert(varName in variables, "Could not set into variables?"); 466 assert(variables[varName].sVar == v, "Could not set into variables?"); 467 namesOrder~= varName; 468 calcAlignment(); 469 470 // import std.stdio; //FIXME: PROBLEM ON WASM 471 // writeln("Created var ", varName, " with hints ", cast(int)v.flags); 472 473 this.data = realloc(this.data, getLayoutSize()); 474 if(!this.data) 475 throw new Exception("Out of memory"); 476 return this; 477 } 478 479 /** 480 * Appends a new variable to this layout. 481 * Type is inferred. 482 */ 483 ShaderVariablesLayout append(T)(string varName, T data) 484 { 485 return append(varName, ShaderVar.create(this.shaderType, varName, data, this)); 486 } 487 /** 488 * Appends a new variable to this layout. 489 * Type is inferred. 490 */ 491 ShaderVariablesLayout appendBlackboxed(string varName, UniformType t, HipRendererInfo info, size_t count, ShaderHint extraFlags) 492 { 493 size_t uSize = info.uniformMapper(shaderType, t); 494 if(uSize == 0) 495 { 496 unusedBlackboxed~= varName; 497 return this; 498 } 499 500 ShaderVar* sV = ShaderVar.createEmpty(this.shaderType, varName, t, uSize*count, uSize, this); 501 if((extraFlags & ShaderHint.MaxTextures) != 0) 502 sV.setDirty(); 503 sV.flags|= extraFlags; 504 sV.flags|= ShaderHint.Blackbox; 505 506 return append(varName, sV); 507 } 508 /** 509 * For speed sake, it doesn't check whether it is valid. 510 * That means both a valid and invalid variable would return false (meaning used.) 511 */ 512 bool isUnused(string varName) @nogc const 513 { 514 foreach(v; unusedBlackboxed) if(v == varName) return true; 515 return false; 516 } 517 518 final size_t getLayoutSize(){return lastPosition;} 519 final void setAdditionalData(void* d, bool isAllocated) 520 { 521 this.additionalData = d; 522 this.isAdditionalAllocated = isAllocated; 523 } 524 final const(void*) getAdditionalData() const {return cast(const(void*))additionalData;} 525 526 auto opDispatch(string member)() 527 { 528 return variables[member].sVar; 529 } 530 531 void dispose() 532 { 533 import core.stdc.stdlib:free; 534 foreach (ref v; variables) 535 { 536 v.sVar.dispose(); 537 v.alignment = 0; 538 v.size = 0; 539 v.sVar = null; 540 } 541 if(data != null) 542 free(data); 543 if(isAdditionalAllocated && additionalData != null) 544 free(additionalData); 545 additionalData = null; 546 data = null; 547 } 548 } 549 550 551 private bool isShaderVarNameValid(ref string varName) 552 { 553 import hip.util.string : indexOf; 554 555 return varName.length > 0 && 556 varName.indexOf(" ") == -1; 557 }